#include <stdlib.h>
#include <stdio.h>
#include<sys/time.h>

#include "od_sip_comn.h"
#include "od_engine.h"
#include "od_h264_sdp_match.h"
#include "od_h265_sdp_match.h"
#include "od_h264_sdp_match_GB28181.h"

/* report level and macro */
#define SIP_ERR (1) /* error, system error */
#define SIP_WRN (2) /* warning, maybe wrong, maybe OK */
#define SIP_INF (3) /* important information */
#define SIP_DBG (4) /* debug information */

#ifndef S_SPLINT_S /* FIXME */
#define SIPERR(fmt, ...) if(SIP_ERR <= rpt_lvl) fprintf(stderr, "%s: %s: %d: err: " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#define SIPWRN(fmt, ...) if(SIP_WRN <= rpt_lvl) fprintf(stderr, "%s: %s: %d: wrn: " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#define SIPINF(fmt, ...) if(SIP_INF <= rpt_lvl) fprintf(stderr, "%s: %s: %d: inf: " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
#define SIPDBG(fmt, ...) if(SIP_DBG <= rpt_lvl) fprintf(stderr, "%s: %s: %d: dbg: " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
static int rpt_lvl = SIP_DBG; /* report level: ERR, WRN, INF, DBG */
#else
#define SIPERR(fmt...)
#define SIPWRN(fmt...)
#define SIPINF(fmt...)
#define SIPDBG(fmt...)
#endif


#define OD_USER_AGENT "suma/2.0"

typedef struct 
{
    /**public parameter
    * see #od_sip_ctx, DO NOT change parameter order!
    */
    eXosip_t *ex_ctx;
    pj_pool_t *pool;
    od_sip_share *share;
    od_sip_callback cb;
    od_sip_engine_cfg engine_cfg;

    /**
    * privite parameter
    */
    uint32_t refcnt;        /**< obj reference count*/
    pj_caching_pool cp;
    OD_MUTEX_ID mutex;
    uint32_t pool_refcnt;   /**< #pj_pool_t *pool reference count*/
}od_sip_ctx_obj;

static od_sip_ctx_obj *g_od_sip_ctx = NULL;
static int opend = 0;

static void sip_engine_exit()
{
    g_od_sip_ctx->refcnt = 0;
    od_sip_comn_deinit();
    SIPWRN("sip_engine_exit OK!\r\n");
}

static od_sip_engine_cfg sip_cfg_get_default()
{
    od_sip_engine_cfg cfg;

    cfg.transport = IPPROTO_UDP;
    cfg.family = AF_INET;
    cfg.local_sip_port = 5060;
    cfg.timer.T1 = 500;
    cfg.timer.T2 = 4000;
    cfg.timer.T4 = 5000;
    cfg.trace_log_lvl = 6;
    cfg.protocol_standard = OD_SIP_PROTOCOL_GB28181_2016;

    return cfg;
}

/*register video codec*/
static void sip_neg_codec_register(od_sip_protocol_standard protocol)
{
    pj_str_t codec_name;
    pj_status_t status;
    pjmedia_sdp_neg_fmt_match_cb h264_func = od_codec_h264_match_sdp;

    if (protocol == OD_SIP_PROTOCOL_GB28181_2016) {
        h264_func = od_codec_h264_match_sdp_GB28181;
    }else if (protocol == OD_SIP_PROTOCOL_GJB10){
        h264_func = od_codec_h264_match_sdp;
    }

    pj_cstr(&codec_name, "H264");  
    status = pjmedia_sdp_neg_register_fmt_match_cb(
                                            &codec_name,
                                            h264_func);
    if (status != PJ_SUCCESS) {
        SIPERR("codec %s registe error!!", pj_strbuf(&codec_name));
    }

    pj_cstr(&codec_name, "H265");  
    status = pjmedia_sdp_neg_register_fmt_match_cb(
                                            &codec_name,
                                            &od_codec_h265_match_sdp);
    if (status != PJ_SUCCESS) {
        SIPERR("codec %s registe error!!", pj_strbuf(&codec_name));
    }
}

int od_sip_comn_init(od_sip_engine_cfg *cfg,
                       od_sip_callback *cb)
{
    od_sip_engine_cfg *cfg_para = NULL;
    pj_status_t status;

    /**
    * eXosip/osip lib can be open only once in a process!
    */
    if (opend)
        return OD_OK;

    OD_SIP_ASSERT_RETURN(cb, OD_ERROR);
        
    g_od_sip_ctx = (od_sip_ctx_obj *)od_calloc(1, sizeof(od_sip_ctx_obj));
    OD_SIP_ASSERT_RETURN(g_od_sip_ctx, OD_ENOMEM);

    g_od_sip_ctx->ex_ctx = (eXosip_t *)od_calloc(1, sizeof(eXosip_t));
    OD_SIP_ASSERT_RETURN(g_od_sip_ctx->ex_ctx, OD_ENOMEM);
    cfg_para = &g_od_sip_ctx->engine_cfg;


    /**
    * usr can use #od_sip_engine_cfg config like #local_sip_port params,
    * if not, use default parameters.
    */
    if (cfg) {
        memcpy(cfg_para, cfg, sizeof(od_sip_engine_cfg));
    } else {
        *cfg_para = sip_cfg_get_default();
        RPTWRN("od_sip_comn_init use default parameter!!");
    }

    /**
    * user can set T1,T2,T4 timer in osip/eXosip lib, note that 
    * function name should be #osip_RTT_set but for some reason,
    * func name write error here #osio_RTT_set :)
    */
    if (osio_RTT_set(cfg_para->timer.T1, 
                     cfg_para->timer.T2, 
                     cfg_para->timer.T4) < 0)
    {
        printf("T1[%d], T2[%d], T4[%d] timer error!!!", cfg_para->timer.T1,
            cfg_para->timer.T2, cfg_para->timer.T4);
        goto exit;
    }

    /*user can open eXosip/osip trace log from here*/
    TRACE_ENABLE_LEVEL(cfg_para->trace_log_lvl);
    TRACE_INITIALIZE(cfg_para->trace_log_lvl, stderr);

    /*init eXosip/osip lib, and let them run!*/
    if (eXosip_init(g_od_sip_ctx->ex_ctx) != 0) {
        SIPERR("eXosip init error!!");
        goto exit;
    }
    if (eXosip_listen_addr(g_od_sip_ctx->ex_ctx, 
                           cfg_para->transport, 
                           NULL, 
                           cfg_para->local_sip_port, 
                           cfg_para->family, 
                           0) != 0) {
        SIPERR("eXosip_listen_addr error!!");
        goto exit;
    }

    /*change user agent strings*/
    eXosip_set_user_agent(g_od_sip_ctx->ex_ctx, OD_USER_AGENT);

    /* Must init PJLIB first */
    status = pj_init();
    if (status != PJ_SUCCESS) {
        SIPERR("pj_init() error!!!");
        goto exit;
    }
    /*init sdp create and parse buffer pool*/      
    pj_caching_pool_init(&g_od_sip_ctx->cp, &pj_pool_factory_default_policy, 0);
    g_od_sip_ctx->pool = pj_pool_create( &g_od_sip_ctx->cp.factory, /* pool factory         */
                                         "od_sip",                  /* pool name.           */
                                         4000,                      /* init size            */
                                         4000,                      /* increment size       */
                                         NULL                       /* callback on error    */
                                         ); 
    if (!g_od_sip_ctx->pool) {
        SIPERR("pj_pool create error!!!");
        goto exit;
    }

    /*init Share region*/
    g_od_sip_ctx->share = (od_sip_share *)od_calloc(1, sizeof(od_sip_share));
    OD_SIP_ASSERT_RETURN(g_od_sip_ctx->share, OD_ENOMEM);
    g_od_sip_ctx->share->se_timer.min_se = 90;
    g_od_sip_ctx->share->se_timer.sess_expires = 90;
    g_od_sip_ctx->share->se_timer.max_fail_times = 3;

    /*init callback functions*/
    OD_SIP_ASSERT_RETURN(cb->cfg_cb.get_capa, OD_ERROR);
    memcpy(&g_od_sip_ctx->cb, cb, sizeof(od_sip_callback));

    /*init privite lock*/
    if (od_mutex_init(&g_od_sip_ctx->mutex) != OD_OK) {
        SIPERR("mutex init error!");
        goto exit;
    }

    /*register video neg codec*/
    sip_neg_codec_register(cfg_para->protocol_standard);


    opend = 1;
    atexit(sip_engine_exit);
    return OD_OK;

exit:
    if (g_od_sip_ctx) {
        if (g_od_sip_ctx->ex_ctx) {
            eXosip_quit(g_od_sip_ctx->ex_ctx);
            od_free(g_od_sip_ctx->ex_ctx);
        }

        if (g_od_sip_ctx->pool) {
            pj_pool_release(g_od_sip_ctx->pool);
            pj_caching_pool_destroy(&g_od_sip_ctx->cp);

            /* Shutdown PJLIB */
            pj_shutdown();
        }
    }
    
    return OD_ERROR;                
}

void od_sip_comn_deinit()
{
    if (!g_od_sip_ctx) 
        return;

    OD_SIP_ASSERT(g_od_sip_ctx->refcnt == 0);

    if (g_od_sip_ctx->ex_ctx) {
        eXosip_quit(g_od_sip_ctx->ex_ctx);
        od_free(g_od_sip_ctx->ex_ctx);
    }

    if (g_od_sip_ctx->pool) {
        pj_pool_release(g_od_sip_ctx->pool);
        pj_caching_pool_destroy(&g_od_sip_ctx->cp);

        /* Shutdown PJLIB */
        pj_shutdown();
    }

    if (g_od_sip_ctx->share)
        od_free(g_od_sip_ctx->share);

    if (od_mutex_destroy(&g_od_sip_ctx->mutex) != OD_OK)
        SIPERR("mutex destroy error!");

    od_free(g_od_sip_ctx);
    g_od_sip_ctx = NULL;
    opend = 0;

    SIPWRN("SIP-engine deinit!!");
}

od_sip_ctx *od_sip_lib_start()
{
    if (g_od_sip_ctx) {
        od_mutex_lock(&g_od_sip_ctx->mutex);
        g_od_sip_ctx->refcnt++;
        od_mutex_unlock(&g_od_sip_ctx->mutex);
        return (od_sip_ctx *)g_od_sip_ctx;
    }

    return NULL;
}

void od_sip_lib_stop()
{
    if (g_od_sip_ctx) {
        od_mutex_lock(&g_od_sip_ctx->mutex);
        if (g_od_sip_ctx->refcnt > 0)
            g_od_sip_ctx->refcnt--;
        od_mutex_unlock(&g_od_sip_ctx->mutex);
    }
}

uint32_t od_sip_getCurTimeInMsec()
{
    static int isInit = OD_FALSE;
    static uint32_t initTime=0;
    struct timeval tv;

    if(isInit == OD_FALSE) {
        if (gettimeofday(&tv, NULL) < 0)
            return 0;

        initTime = (uint32_t)(tv.tv_sec * 1000u + tv.tv_usec/1000u);
        isInit = OD_TRUE;
    }

    if (gettimeofday(&tv, NULL) < 0)
        return 0;

    return (uint32_t)(tv.tv_sec * 1000u + tv.tv_usec/1000u) - initTime;
}

void od_sip_clear_event_fifo(pipeline_handle fifo)
{
    eXosip_event_t * event = NULL;
    
    while(pipeline_get_nums(fifo) > 0)
    {
        pipeline_get(fifo, &event);
        eXosip_event_free(event);
    }
}

int od_sip_get_event(pipeline_handle pipe, eXosip_event_t **event)
{
    if(pipeline_get_nums(pipe) <= 0) 
        return OD_ERROR;

    return pipeline_get(pipe, event);
}

void od_sip_send_ack(eXosip_t *exctx, eXosip_event_t *event)
{
    osip_message_t *ack = NULL;

    OD_SIP_ASSERT(exctx && event);

    eXosip_lock(exctx);
    eXosip_call_build_ack(exctx, event->did, &ack);  
    eXosip_call_send_ack(exctx, event->did, ack);
    eXosip_unlock(exctx);
}

void od_sip_call_send_answer_with_code(eXosip_t *exctx, eXosip_event_t *event, int status)
{
    osip_message_t *answer = NULL;
    
    OD_SIP_ASSERT(exctx && event);

    eXosip_lock(exctx);
    if (!eXosip_call_build_answer(exctx, event->tid, status, &answer)) {
        eXosip_call_send_answer(exctx, event->tid, status, answer);
    }
    eXosip_unlock(exctx);
}

void od_sip_send_answer_with_code(eXosip_t *exctx, eXosip_event_t *event, int status)
{  
    osip_message_t *answer = NULL;
    
    eXosip_lock(exctx);
    if (!eXosip_message_build_answer(exctx, event->tid, status, &answer)) {
        eXosip_message_send_answer(exctx, event->tid, status, answer);
    }
    eXosip_unlock(exctx);
}

/*_*/

